package com.foxsteps.gsonformat.helper;

import com.foxsteps.gsonformat.apiutils.*;
import com.foxsteps.gsonformat.component.operation.AnnotionDocWriter;
import com.foxsteps.gsonformat.entity.MapStructAutoModel;
import com.foxsteps.gsonformat.enums.AnnontionDocElements;
import com.foxsteps.gsonformat.enums.AnnotationClass;
import com.foxsteps.gsonformat.enums.HttpRequestMethod;
import com.foxsteps.gsonformat.enums.MapstructModeEnum;
import com.foxsteps.gsonformat.manager.ApiDocActionManger;
import com.foxsteps.gsonformat.model.*;
import com.foxsteps.gsonformat.model.apidoc.ApiDocCommentBean;
import com.foxsteps.gsonformat.ui.ApiDocGenerateDialog;
import com.foxsteps.gsonformat.utils.PsiElementUtils;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocComment;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.stream.Collectors;


/**
 * @author dongxuanliang252
 * @date 2018-12-18 12:05
 */
public enum DialogHelper {

    INSTANCE;

    private AnnotionDocWriter annotionDocWriter = ServiceManager.getService(AnnotionDocWriter.class);

    public void showGenerateDialog(DialogModel model, PsiElement psiElement) {
        ApiDocGenerateDialog dialog = new ApiDocGenerateDialog(false, model, psiElement);
        dialog.show();
    }

    public DialogModel parseOldDialogModel(PsiDocComment docComment) {
        ApiDocCommentBean commentBean = ApiDocElementUtils.INSTANCE.parse(docComment);
        return ConvertUtils.INSTANCE.convertCommentBean2DialogModel(commentBean);
    }

    public void writeJavaDoc(DialogModel model, PsiElement psiElement) {
        processDialogModel(model, psiElement);
        List<MapStructAutoModel> existTarget = processAllOrAddModel(model,psiElement);
        PsiAnnotation javaDoc = PsiDocCommentUtils.INSTANCE.createPsiAnnotation(buildMapStructAutoText(model,existTarget), psiElement);
        annotionDocWriter.write(model,javaDoc, psiElement);
    }

    public static List<MapStructAutoModel> processAllOrAddModel(DialogModel model, PsiElement psiElement) {
        List<MapStructAutoModel> existTarget = Lists.newArrayList();
        if(!(model.getMode().equals(MapstructModeEnum.ADD.getCode()))){
            return existTarget;
        }
        ResultBean resultBean = model.getResponseBody();
        if(Objects.isNull(resultBean)){
            return existTarget;
        }
        PsiElement annotation = PsiElementUtils.checkReplaceJavaDoc(psiElement);
        if(Objects.isNull(annotation)){
            return existTarget;
        }
        try {
            for(PsiElement subAnnotation : annotation.getChildren()){
                if(!(subAnnotation instanceof PsiAnnotationParameterList)){
                    continue;
                }
                PsiAnnotationParameterList psiAnnotationParameterList = (PsiAnnotationParameterList) subAnnotation;
                PsiNameValuePair[] psiNameValuePairs = psiAnnotationParameterList.getAttributes();
                for(PsiNameValuePair pair : psiNameValuePairs){
                    PsiAnnotationMemberValue psiAnnotationMemberValue = pair.getValue();
                    for(PsiElement sub : psiAnnotationMemberValue.getChildren()){
                        if(!(sub instanceof PsiAnnotation)){
                            continue;
                        }
                        PsiAnnotation psiAnnotation = (PsiAnnotation) sub;
                        boolean checkFlag = PsiAnnotationUtils.INSTANCE.isAssignableFrom(AnnotationClass.MASTRUCT_MAPPING_DETAIL.getClassName(), psiAnnotation);
                        if(!checkFlag){
                            continue;
                        }
                        for(PsiElement psiElement1 : psiAnnotation.getChildren()){
                            if(!(psiElement1 instanceof PsiAnnotationParameterList)){
                                continue;
                            }
                            MapStructAutoModel mapStructAutoModel = new MapStructAutoModel();
                            PsiAnnotationParameterList annotationParameterList = (PsiAnnotationParameterList) psiElement1;
                            PsiNameValuePair[] pairs = annotationParameterList.getAttributes();
                            for(PsiNameValuePair pair1 : pairs){
                                if(("source".equals(pair1.getAttributeName()))){
                                    mapStructAutoModel.setSource(pair1.getValue().getText().trim().replaceAll("\"",""));
                                }
                                if(("target".equals(pair1.getAttributeName()))){
                                    mapStructAutoModel.setTarget(pair1.getValue().getText().replaceAll("\"",""));
                                }
                            }
                            mapStructAutoModel.setOldData(psiAnnotation.getText());
                            if(StringUtils.isNotEmpty(mapStructAutoModel.getSource()) &&
                                    StringUtils.isNotEmpty(mapStructAutoModel.getTarget())){
                                existTarget.add(mapStructAutoModel);
                            }
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return existTarget;
    }

    public static List<MapStructAutoModel> getOldMapping(DialogModel model, PsiElement psiElement) {
        List<MapStructAutoModel> existTarget = Lists.newArrayList();
        ResultBean resultBean = model.getResponseBody();
        if(Objects.isNull(resultBean)){
            return existTarget;
        }
        PsiElement annotation = PsiElementUtils.checkReplaceJavaDoc(psiElement);
        if(Objects.isNull(annotation)){
            return existTarget;
        }
        try {
            for(PsiElement subAnnotation : annotation.getChildren()){
                if(!(subAnnotation instanceof PsiAnnotationParameterList)){
                    continue;
                }
                PsiAnnotationParameterList psiAnnotationParameterList = (PsiAnnotationParameterList) subAnnotation;
                PsiNameValuePair[] psiNameValuePairs = psiAnnotationParameterList.getAttributes();
                for(PsiNameValuePair pair : psiNameValuePairs){
                    PsiAnnotationMemberValue psiAnnotationMemberValue = pair.getValue();
                    for(PsiElement sub : psiAnnotationMemberValue.getChildren()){
                        if(!(sub instanceof PsiAnnotation)){
                            continue;
                        }
                        PsiAnnotation psiAnnotation = (PsiAnnotation) sub;
                        boolean checkFlag = PsiAnnotationUtils.INSTANCE.isAssignableFrom(AnnotationClass.MASTRUCT_MAPPING_DETAIL.getClassName(), psiAnnotation);
                        if(!checkFlag){
                            continue;
                        }
                        for(PsiElement psiElement1 : psiAnnotation.getChildren()){
                            if(!(psiElement1 instanceof PsiAnnotationParameterList)){
                                continue;
                            }
                            MapStructAutoModel mapStructAutoModel = new MapStructAutoModel();
                            PsiAnnotationParameterList annotationParameterList = (PsiAnnotationParameterList) psiElement1;
                            PsiNameValuePair[] pairs = annotationParameterList.getAttributes();
                            for(PsiNameValuePair pair1 : pairs){
                                if(("source".equals(pair1.getAttributeName()))){
                                    mapStructAutoModel.setSource(pair1.getValue().getText().trim().replaceAll("\"",""));
                                }
                                if(("target".equals(pair1.getAttributeName()))){
                                    mapStructAutoModel.setTarget(pair1.getValue().getText().replaceAll("\"",""));
                                }
                            }
                            if(StringUtils.isNotEmpty(mapStructAutoModel.getSource()) &&
                                    StringUtils.isNotEmpty(mapStructAutoModel.getTarget())){
                                existTarget.add(mapStructAutoModel);
                            }
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return existTarget;
    }

    private void removeTarget(List<FieldBean> fieldBeanList, List<String> existTarget) {
        if(CollectionUtils.isEmpty(fieldBeanList) || CollectionUtils.isEmpty(existTarget)){
            return;
        }
        for(FieldBean fieldBean : fieldBeanList){
            if(CollectionUtils.isNotEmpty(fieldBean.getChildFieldList())){
                removeTarget(fieldBean.getChildFieldList(),existTarget);
                continue;
            }
            if(existTarget.contains(fieldBean.getName())){
                fieldBean.setIgnoreFlag(true);
            }
        }
    }

    public static String buildMapStructAutoText(DialogModel model,List<MapStructAutoModel> existTarget) {
        ParamBean paramBean = model.getRequestBody();
        ResultBean resultBean = model.getResponseBody();
        if(Objects.isNull(paramBean) || Objects.isNull(resultBean)){
            return AnnontionDocElements.EMPTY.getPresentation();
        }
        if(CollectionUtils.isEmpty(paramBean.getFieldList()) || CollectionUtils.isEmpty(resultBean.getFieldList())){
            return AnnontionDocElements.EMPTY.getPresentation();
        }
        StringBuilder sb = new StringBuilder();
        sb.append(AnnontionDocElements.MAPPINGS.getPresentation());
        List<String> paramList = buildParamList(AnnontionDocElements.EMPTY.getPresentation(),paramBean.getFieldList());
        List<String> resultList = buildParamList(AnnontionDocElements.EMPTY.getPresentation(),resultBean.getFieldList());
        if(CollectionUtils.isEmpty(paramList) || CollectionUtils.isEmpty(resultList)){
            return AnnontionDocElements.EMPTY.getPresentation();
        }
        paramList.sort(Comparator.comparingInt(String::length));
        List<MapStructAutoModel> mapStructAutoModels = Lists.newArrayList();
        useExistTarget(mapStructAutoModels,existTarget);
        List<String> uniqueKey = convertExistTarget(existTarget);
        for(String target : resultList) {
            if(ApiDocActionManger.FILTER_FIELD.contains(target)){
                continue;
            }
            List<String> sources = searchSource(target, paramList);
            if (CollectionUtils.isEmpty(sources)) {
                continue;
            }
            for (String source : sources) {
                if (checkExistTarget(source, target, uniqueKey)) {
                    continue;
                }
                mapStructAutoModels.add(new MapStructAutoModel(source, target));
            }
        }
        if(CollectionUtils.isEmpty(mapStructAutoModels)){
            return AnnontionDocElements.EMPTY.getPresentation();
        }
        return buildMapper(sb,mapStructAutoModels);
    }

    public static String buildExistMapStructAutoText(List<MapStructAutoModel> existTarget) {
        StringBuilder sb = new StringBuilder();
        sb.append(AnnontionDocElements.MAPPINGS.getPresentation());
        return buildMapper(sb,existTarget);
    }

    private static List<String> convertExistTarget(List<MapStructAutoModel> existTarget) {
        List<String> uniqueKey = Lists.newArrayList();
        if(CollectionUtils.isEmpty(existTarget)){
            return uniqueKey;
        }
        for(MapStructAutoModel model : existTarget){
            if(StringUtils.isNotEmpty(model.getSource()) && StringUtils.isNotEmpty(model.getTarget())){
                uniqueKey.add(String.format("%s-%s",model.getSource(),model.getTarget()));
            }
        }
        return uniqueKey;
    }

    private static void useExistTarget(List<MapStructAutoModel> mapStructAutoModels, List<MapStructAutoModel> existTarget) {
        if(CollectionUtils.isEmpty(existTarget)){
            return;
        }
        mapStructAutoModels.addAll(existTarget);
    }

    private static boolean checkExistTarget(String source,String target, List<String> existTarget) {
        if(CollectionUtils.isEmpty(existTarget)){
            return false;
        }
        return existTarget.contains(String.format("%s-%s",source,target));
    }

    /**
     * @author chenfoxin
     * @description 搜索来源
     * @param target
     * @param paramList
     * @date: 2023/4/2 10:20
     * @return java.lang.String
     */
    private static List<String> searchSource(String target, List<String> paramList){
        List<String> sources = Lists.newArrayList();
        if(CollectionUtils.isEmpty(paramList)){
            return sources;
        }
        String[] targetArray = target.split(AnnontionDocElements.DIAN.getPresentation());
        if(targetArray.length == 0){
            return sources;
        }
        String result = AnnontionDocElements.EMPTY.getPresentation();
        int depth = 0;
        for(String param : paramList){
            String[] paramArray = param.split(AnnontionDocElements.DIAN.getPresentation());
            if(paramArray.length == 0){
                continue;
            }
            if(targetArray[targetArray.length - 1].equals(paramArray[paramArray.length - 1])){
                sources.add(param);
            }
//            int matchDepth = fetchDepth(paramArray,targetArray);
//            if(matchDepth > depth){
//                depth = matchDepth;
//                result = param;
//            }
        }
        return sources;
    }

    /**
     * @author chenfoxin
     * @description 获取比较深度
     * @param s
     * @param r
     * @date: 2023/4/2 10:38
     * @return int
     */
    private static int fetchDepth(String[] s, String[] r) {
        int min = Math.min(s.length, r.length);
        int matchDepth = 0;
        for(int i = 0;i < min;i++){
            int sIndex = s.length - 1 - i;
            int rIndex = r.length - 1 - i;
            if(s[sIndex].equals(r[rIndex])){
                matchDepth++;
            }
        }
        return matchDepth;
    }

    private static String buildMapper(StringBuilder sb, List<MapStructAutoModel> autoModels) {
        if(CollectionUtils.isEmpty(autoModels)){
            return AnnontionDocElements.EMPTY.getPresentation();
        }
        for(int i =0;i< autoModels.size();i++){
            MapStructAutoModel pair = autoModels.get(i);
            if(StringUtils.isNotEmpty(pair.getOldData())){
                sb.append(pair.getOldData());
            }else {
                sb.append(String.format(AnnontionDocElements.MAPPING_BODY.getPresentation(),pair.getSource(),pair.getTarget()));
            }
            if(i == autoModels.size() - 1){
                sb.append(String.format(AnnontionDocElements.MAPPING_BODY_END.getPresentation(),pair.getSource(),pair.getTarget()));
            }else {
                sb.append(String.format(AnnontionDocElements.MAPPING_BODY_SPLIT.getPresentation(),pair.getSource(),pair.getTarget()));
            }
        }
        return sb.toString();
    }

    public static List<String> buildParamList(String prefix,List<FieldBean> fieldBeanList) {
        List<String> paramResult = Lists.newArrayList();
        if(CollectionUtils.isEmpty(fieldBeanList)){
            return paramResult;
        }
        for(FieldBean fieldBean : fieldBeanList) {
            if(fieldBean.isIgnoreFlag()){
                continue;
            }
            if(AnnontionDocElements.OBJECT.getPresentation().equals(fieldBean.getType())
                    && CollectionUtils.isNotEmpty(fieldBean.getChildFieldList())){
                if(StringUtils.isNotEmpty(prefix)){
                    paramResult.addAll(buildParamList(prefix + "." + fieldBean.getName(),fieldBean.getChildFieldList()));
                }else {
                    paramResult.addAll(buildParamList(fieldBean.getName(),fieldBean.getChildFieldList()));
                }
            }else if(StringUtils.isNotEmpty(prefix)){
                paramResult.add(prefix + "." +fieldBean.getName());
            }else {
                paramResult.add(fieldBean.getName());
            }
        }
        return paramResult;
    }

    private void processDialogModel(DialogModel model, @NotNull PsiElement psiElement) {
        if (model == null) {
            return;
        }
        ParamBean requestParameter = model.getRequestParameter();
        if (requestParameter != null) {
            filterDialogModelFieldBeanRecursively(requestParameter.getFieldList());
            if (StringUtils.isBlank(requestParameter.getExample()) || BooleanUtils.isTrue(model.getReGenerateExampleFlag())) {
                requestParameter.setExample(ExampleUtils.INSTANCE.generateRequestParameterExampleString(requestParameter.getFieldList(), psiElement));
            }
        }
        ParamBean requestBody = model.getRequestBody();
        if (requestBody != null) {
            filterDialogModelFieldBeanRecursively(requestBody.getFieldList());
            if (StringUtils.isBlank(requestBody.getExample()) || BooleanUtils.isTrue(model.getReGenerateExampleFlag())) {
                requestBody.setExample(ExampleUtils.INSTANCE.generateRequestBodyExampleString(requestBody.getFieldList(), psiElement));
            }
        }
        ResultBean responseBody = model.getResponseBody();
        if (responseBody != null) {
            filterDialogModelFieldBeanRecursively(responseBody.getFieldList());
            if (StringUtils.isBlank(responseBody.getExample()) || BooleanUtils.isTrue(model.getReGenerateExampleFlag())) {
                responseBody.setExample(ExampleUtils.INSTANCE.generateResponseBodyExampleString(responseBody.getFieldList(), psiElement));
            }
        }
    }

    private void filterDialogModelFieldBeanRecursively(List<FieldBean> fieldBeanList) {
        if (CollectionUtils.isEmpty(fieldBeanList)) {
            return;
        }
        Iterator<FieldBean> itr = fieldBeanList.iterator();
        while (itr.hasNext()) {
            FieldBean fieldBean = itr.next();
            if (!fieldBean.isChecked()) {
                itr.remove();
            }
            filterDialogModelFieldBeanRecursively(fieldBean.getChildFieldList());
        }
    }

    private String buildCommentText(DialogModel model) {
        ApiDocCommentBean commentBean = ConvertUtils.INSTANCE.convertDialogModel2CommentBean(model);
        return ApiDocElementUtils.INSTANCE.build(commentBean);
    }

    public DialogModel createNewDialogModel(PsiMethod element) {
        if (element == null) {
            return null;
        }
        DialogModel dialogModel = new DialogModel();
        dialogModel.setRequestBody(NewDialogModelParseUtils.INSTANCE.parseRequestBody(element));
        dialogModel.setResponseBody(NewDialogModelParseUtils.INSTANCE.parseResponseBody(element));
        return dialogModel;
    }

    public DialogModel mergeDialogModel(DialogModel newModel, DialogModel oldModel) {
        if (newModel == null && oldModel == null) {
            return null;
        }
        if (newModel == null) {
            return oldModel;
        }
        if (oldModel == null) {
            if(StringUtils.isBlank(newModel.getRequestMethod())){
                newModel.setRequestMethod(HttpRequestMethod.GET.name());
            }
            return newModel;
        }
        DialogModel mergeModel = newModel;
        if (StringUtils.isNotBlank(oldModel.getGroupName())) {
            mergeModel.setGroupName(oldModel.getGroupName());
        }
        if (StringUtils.isNotBlank(oldModel.getName())) {
            mergeModel.setName(oldModel.getName());
        }
        if (StringUtils.isNotBlank(oldModel.getDescription())) {
            mergeModel.setDescription(oldModel.getDescription());
        }
        if (StringUtils.isNotBlank(oldModel.getVersion())) {
            mergeModel.setVersion(oldModel.getVersion());
        }
        if (StringUtils.isNotBlank(oldModel.getRequestTitle())) {
            mergeModel.setRequestTitle(oldModel.getRequestTitle());
        }
        if (StringUtils.isBlank(mergeModel.getRequestUrl())) {
            mergeModel.setRequestUrl(oldModel.getRequestUrl());
        }
        if (StringUtils.isBlank(mergeModel.getRequestMethod())) {
            if (StringUtils.isNotBlank(oldModel.getRequestMethod())) {
                mergeModel.setRequestMethod(oldModel.getRequestMethod());
            } else {
                mergeModel.setRequestMethod(HttpRequestMethod.GET.name());
            }
        }
        mergeModel.setRequestParameter(mergeExample(newModel.getRequestParameter(), oldModel.getRequestParameter()));
        mergeModel.setRequestBody(mergeExample(newModel.getRequestBody(), oldModel.getRequestBody()));
        mergeModel.setResponseBody(mergeExample(newModel.getResponseBody(), oldModel.getResponseBody()));
        return mergeModel;
    }

    private <T extends AbstractExampleBean> List<T> mergeExampleList(List<T> newBeanList, List<T> oldBeanList) {
        if (CollectionUtils.isEmpty(newBeanList)) {
            return null;
        }
        if (CollectionUtils.isEmpty(oldBeanList)) {
            return newBeanList;
        }
        Map<String, AbstractExampleBean> oldBeanMap = oldBeanList.stream().collect(Collectors.toMap(o -> o.getTitle(), o -> o));
        for (AbstractExampleBean newBean : newBeanList) {
            String title = newBean.getTitle();
            if (oldBeanMap.containsKey(title)) {
                AbstractExampleBean oldBean = oldBeanMap.get(title);
                mergeExample(newBean, oldBean);
            }
        }
        return newBeanList;
    }

    private <T extends AbstractExampleBean> T mergeExample(T newBean, T oldBean) {
        if (newBean == null) {
            return null;
        }
        if (oldBean == null) {
            return newBean;
        }
        newBean.setFieldList(FieldBeanTreeUtils.INSTANCE.merge(newBean.getFieldList(), oldBean.getFieldList()));
        if (StringUtils.isBlank(newBean.getExample())) {
            newBean.setExample(oldBean.getExample());
        }
        return newBean;
    }

}
